package org.wcms.surrounded.web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.wcms.surrounded.Constants;
import org.wcms.surrounded.model.User;
import org.wcms.surrounded.persistence.KeyValueService;
import org.wcms.surrounded.persistence.UserService;
import org.wcms.surrounded.utils.Utils;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;
import com.restfb.Parameter;
import com.restfb.exception.FacebookException;
import com.restfb.exception.FacebookGraphException;
import com.restfb.json.JsonObject;

@Singleton
public class FBLoginServlet extends HttpServlet {

    private static final String OAUTH_URL = "https://graph.facebook.com/oauth/access_token";

    private static final long serialVersionUID = -2095906089094922492L;

    private final UserService userService; 
	private final String clientSecret;
	
	@Inject
	public FBLoginServlet(KeyValueService keyValueService, UserService userService) {
	    this.clientSecret = keyValueService.get(Constants.FACEBOOK_CLIENT_SECRET);
	    this.userService = userService;
	}
	
	@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        handle(req, resp);
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        handle(req, resp);
    }
	
    protected void handle(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {		
    	
        String scheme = "http";
        
        if (req.getScheme() != null && req.getScheme().contains("https")) {
            scheme = "https";
        }
        
        RequestDispatcher dispatch = getServletContext().getRequestDispatcher("/postlogin.jsp");
        
        if (dispatch == null) {
            throw new ServletException("Unable to find post login page");
        }
        
        req.setAttribute(Constants.FACEBOOK_SUCCESSFUL_LOGIN, false);
        
		String appCode = req.getParameter("code");
		
		if (appCode == null) {
            dispatch.forward(req, resp);
			return;
		}
       
		String accessToken = getUserAccessToken(scheme, appCode);
		if (accessToken == null) {
            dispatch.forward(req, resp);
            return;
		}
		
		User user = userService.getUserByToken(accessToken);
		if (user == null) {
			try {				
				user = retrieveUserFromFB(accessToken);
				userService.addUser(user);
			} catch (FacebookException t) {           
	            //TODO: handle this somehow
	        }
		}
		
		req.setAttribute("userName", user.getUserName());
		req.setAttribute("userScore", user.getScore());
		req.setAttribute(Constants.FACEBOOK_SUCCESSFUL_LOGIN, true);
		Utils.updateUserSessionAndCookie(req, resp, user);
        dispatch.forward(req, resp);
        
	}

    private User retrieveUserFromFB(String accessToken) {
        //TODO: check if user hasen't disabled any of the requested pemissions - Ran: no need..?

        FacebookClient facebook = new DefaultFacebookClient(accessToken);
        
        JsonObject userData = facebook.fetchObject("me", JsonObject.class, Parameter.with("fields", "id,name,picture"));
        
        if (!userData.has("id") || !userData.has("name") || !userData.has("picture")) {
        	throw new FacebookGraphException("0", "public fields missing from response");
        }
        
        
        String userFBId = (String) userData.get("id");
        User user = userService.getUserByfbId(userFBId);
        if (user == null) {
        
	        user = new User();
	        
	        user.setFbId(userFBId);
	        user.setUserName((String)userData.get("name"));
	        user.setPicURL((String)userData.get("picture"));	        
	        user.setRegistered(true);
	        user.setScore(0);
        }	        
        
        user.setAccessToken(accessToken); //refreshing the user's access token even if he already existed
        
        return user;
    }
    
    private String getUserAccessToken(String scheme, String code) throws IOException {
        StringBuilder params = new StringBuilder()
            .append("&code=").append(code)
            .append("&redirect_uri=").append(URLEncoder.encode(scheme + Constants.FACEBOOK_URL_REDIRECT_SUFFIX, "UTF-8"));
        return getAccessToken(params.toString());
    }
    
    private String getAppAccessToken() throws IOException {
        String params = "&grant_type=client_credentials";
        return getAccessToken(params);
    }
    
    private String getAccessToken(String params) throws IOException {
        
        StringBuilder constantParams = new StringBuilder()
            .append("client_id=").append(Constants.FACEBOOK_CLIENT_ID)
            .append("&client_secret=").append(clientSecret);
        
        params = constantParams.append(params).toString();
        
        URL url = new URL(OAUTH_URL);
        
        URLConnection conn = url.openConnection();
        try {           
            conn.setDoInput(true);
            conn.setDoOutput(true);             
            conn.connect();
                        
            conn.getOutputStream().write(params.toString().getBytes());
            conn.getOutputStream().flush();
        }
        finally {
            conn.getOutputStream().close();
        }
        
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        try {
            
            String inputLine = in.readLine();
            if (inputLine == null || !inputLine.contains("=")) {
                return null;
            }

            // isn't there an extra expired time param?
            String accessToken = inputLine.split("=")[1];       
            
            return accessToken;

        }
        finally {       
            in.close();
        }
        
    }
    
}
